package com.guigusuqi.commonutils.aop;

import com.alibaba.fastjson.JSON;
import com.guigusuqi.commonutils.annotation.Cache;
import com.guigusuqi.commonutils.config.ScheduledTaskThread;
import com.guigusuqi.commonutils.dto.ProceedingJoinPointDTO;
import com.guigusuqi.commonutils.utils.RedisCache;

import com.guigusuqi.commonutils.utils.ThreadPoolConfig;
import com.guigusuqi.commonutils.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@Aspect
@Component
@Slf4j
public class CacheAspect
{
    @Resource
    private RedisCache redisCache;

    @Resource
    private ThreadPoolConfig threadPoolConfig;


    //  加入了该注解表示成为了一个切点
    @Pointcut("@annotation(com.guigusuqi.commonutils.annotation.Cache)")
    public void pt(){}

    //  环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable
    {
        try {
            Signature signature = joinPoint.getSignature();

            //获得切点的类名
            String className = joinPoint.getTarget().getClass().getSimpleName();

            //获得切点的方法名
            String methodName = signature.getName();

            //存储参数类型的数组 类型
            Class[] parameterTypes = new Class[joinPoint.getArgs().length];

            //前端传递的参数类型的数组
            Object[] args = joinPoint.getArgs();

            //存储每个参数的类型,redis把该params作为key
            StringBuilder params = new StringBuilder();

            for (int i = 0; i < args.length; i++)
            {
                if(args[i] != null)
                {
                    params.append(JSON.toJSONString(args[i]));//获得参数
                    parameterTypes[i] = args[i].getClass();//获得参数的类型
                }else
                {
                    parameterTypes[i] = null;
                }
            }
            if (StringUtils.isNotEmpty(params.toString()))
            {
                //MD5加密 以防出现key过长以及字符串转义获取不到的情况
                params = new StringBuilder(DigestUtils.md5Hex(params.toString()));
            }
            //通过parameterTypes获得切点
            Method method = signature.getDeclaringType().getMethod(methodName, parameterTypes);

            //获得该切点方法上的cache注解
            Cache cache = method.getAnnotation(Cache.class);

            //获得切点设置的过期时间
            long expire = cache.expire();

            //获得切点设置的缓存名称
            String name = cache.name();

            //先从redis获得，看是否该缓存存入了redis  缓存名::类名::方法名::参数
            String redisKey = name + "::" + className + "::" + methodName + "::" + params;
            String redisValue = redisCache.getCacheObject(redisKey);

            //如果redisValue不为空，代表走了缓存
            if(StringUtils.isNotEmpty(redisValue))
            {
                log.info("该接口{}.{}已缓存",className,methodName);
                ScheduledTaskThread scheduledTaskThread = new ScheduledTaskThread(new ProceedingJoinPointDTO(joinPoint,redisKey,expire));
                ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = threadPoolConfig.getScheduledThreadPoolExecutor();
                scheduledThreadPoolExecutor.schedule(scheduledTaskThread,5, TimeUnit.SECONDS);
                return JSON.parseObject(redisValue, Result.class);
            }
            //如果为空，代表需要调用切点方法,再把返回值存入redis里面
            Object proceed = joinPoint.proceed();
            log.info("proceed----");
            log.info(JSON.toJSONString(proceed));
            log.info("----proceed");
            redisCache.setCacheObject(redisKey, JSON.toJSONString(proceed), Duration.ofMillis(expire));
            log.info("接口{}.{}存入缓存",className,methodName);
            return proceed;

        }catch (Throwable throwable)
        {
            throwable.printStackTrace();
        }
        return Result.fail().code(-999).message("系统错误");
    }
}
